package hmac

/*
#include <stdlib.h>
#include <stdio.h>
#include "crypt_errno.h"
#include "crypt_algid.h"
#include "crypt_eal_mac.h"
*/
import "C"
import (
	"fmt"
	"unsafe"
)

type HMACContext struct {
	ctx unsafe.Pointer // 维护 CRYPT_EAL_MacCtx 指针
}

// copy from "crypt_algid.h" CRYPT_MAC_AlgId enum
const (
	HMAC_MD5      int = 10501
	HMAC_SHA1     int = 10502
	HMAC_SHA224   int = 10503
	HMAC_SHA256   int = 10504
	HMAC_SHA384   int = 10505
	HMAC_SHA512   int = 10506
	HMAC_SHA3_224 int = 10507
	HMAC_SHA3_256 int = 10508
	HMAC_SHA3_384 int = 10509
	HMAC_SHA3_512 int = 10510
	HMAC_SM3      int = 10511
)
const HMAC_MAXOUTSIZE int = 64

func NewCTX(algorithm int) *HMACContext {
	ctx := C.CRYPT_EAL_MacNewCtx(C.CRYPT_MAC_AlgId(algorithm))
	if ctx == nil {
		panic("Failed  to create HMAC context")
	}
	return &HMACContext{ctx: unsafe.Pointer(ctx)}
}

func (p *HMACContext) Init(key []byte) error {
	if p.ctx == nil {
		return fmt.Errorf("HMAC update: Context Nil")
	}
	ret := C.CRYPT_EAL_MacInit((*C.CRYPT_EAL_MacCtx)(p.ctx), (*C.uint8_t)(&key[0]), C.uint(len(key)))
	if ret != 0 {
		fmt.Errorf("HMAC init failed with error code: %d", ret)
	}
	return nil
}

func (p *HMACContext) Update(data []byte) error {
	if p.ctx == nil {
		return fmt.Errorf("HMAC update: Context Nil")
	}
	ret := C.CRYPT_EAL_MacUpdate((*C.CRYPT_EAL_MacCtx)(p.ctx), (*C.uint8_t)(&data[0]), C.uint(len(data)))
	if ret != 0 {
		fmt.Errorf("HMAC update failed with error code: %d", ret)
	}
	return nil
}

func (p *HMACContext) Final() ([]byte, error) {
	if p.ctx == nil {
		return nil, fmt.Errorf("HMAC final: Context Nil")
	}
	out := make([]byte, HMAC_MAXOUTSIZE)
	outLen := C.uint(len(out))
	ret := C.CRYPT_EAL_MacFinal((*C.CRYPT_EAL_MacCtx)(p.ctx), (*C.uint8_t)(&out[0]), &outLen)
	if ret != 0 {
		return nil, fmt.Errorf("HMAC final failed with error code: %d", ret)
	}

	return out[:outLen], nil
}

func (p *HMACContext) Free() error {
	if p.ctx == nil {
		return nil
	}
	C.CRYPT_EAL_MacFreeCtx((*C.CRYPT_EAL_MacCtx)(p.ctx))
	p.ctx = nil
	return nil
}

func (p *HMACContext) DeInit() error {
	if p.ctx == nil {
		return fmt.Errorf("HMAC deinit: Context Nil")
	}
	ret := C.CRYPT_EAL_MacDeinit((*C.CRYPT_EAL_MacCtx)(p.ctx))
	if ret != 0 {
		return fmt.Errorf("HMAC deinit failed with error code: %d", ret)
	}
	return nil
}

func (p *HMACContext) ReInit() error {
	if p.ctx == nil {
		return fmt.Errorf("HMAC reinit: Context Nil")
	}
	ret := C.CRYPT_EAL_MacReinit((*C.CRYPT_EAL_MacCtx)(p.ctx))
	if ret != 0 {
		return fmt.Errorf("HMAC reinit failed with error code: %d", ret)
	}
	return nil
}

func (p *HMACContext) GetLen() (int, error) {
	if p.ctx == nil {
		return 0, fmt.Errorf("HMAC getlen: Context Nil")
	}
	ret := C.CRYPT_EAL_GetMacLen((*C.CRYPT_EAL_MacCtx)(p.ctx))
	return int(ret), nil
}
